package org.aerie.forest.extensions.base.rebar.tool.math;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;

import org.aerie.forest.core.brick.exception.ExceptionPack;
import org.aerie.forest.extensions.base.brick.exception.MathParaException;

/**
 * @description 开方动态精度包
 *
 * @author quark
 * @organization aerie
 * @date 2020年4月15日 下午1:05:17
 * @version 1.2.0
 */
public class SqrtDynamicScalePack {

	/**
	 * @description 二十【偏移量系数】
	 * 
	 * @author quark
	 * @date 2020年4月15日 上午11:06:38
	 */
	private static final BigDecimal TWENTY = new BigDecimal("20");

	/**
	 * @description 底数
	 * 
	 * @author quark
	 * @date 2020年4月15日 上午10:57:53
	 */
	private BigDecimal base;

	/**
	 * @description 指数
	 * 
	 * @author quark
	 * @date 2020年4月15日 上午10:58:52
	 */
	private int exponent;
	/**
	 * @description 动态精度层【因为本地方法精度15以上】
	 * 
	 * @author quark
	 * @date 2020年4月15日 上午10:35:04
	 */
	private int dynamicScaleLayer = 15;

	/**
	 * @description 指定精度层动态自适应偏移量
	 * 
	 * @author quark
	 * @date 2020年4月15日 上午10:36:05
	 */
	private BigDecimal dynamicOffsetOfX;

	/**
	 * @description 精度值动态偏移量
	 * 
	 * @author quark
	 * @date 2020年4月15日 上午10:36:54
	 */
	private BigDecimal dynamicOffsetOfY;

	/**
	 * Construction
	 * 
	 * @param base     底数
	 * @param exponent 指数
	 * @throws MathParaException
	 *
	 * @date 2020年4月15日 上午11:01:15
	 */
	public SqrtDynamicScalePack(BigDecimal base, int exponent) throws Exception {
		super();
		this.base = base;
		this.exponent = exponent;
		// 根据二项式计算得出的精度层动态偏移量系数【保证之后的每一项的偏移量低一个数量级】
//		if (5 * (exponent - 1) * getDynamicScaleByLayer(exponent) / exponent
//				/ base.pow(exponent).compareTo(BigDecimal.ONE)) {
//
//		}
		try {
			selfAdaptionByDynamicOffsetOfY();
		} catch (Exception e) {
			throw new ExceptionPack("创建开方动态精度包异常", e);
		}
	}

	/**
	 * @description 获得底数
	 * @return 底数
	 *
	 * @date 2020年4月15日 下午1:27:31
	 */
	public BigDecimal getBase() {
		return base;
	}

	/**
	 * @description 获得指数
	 * @return 指数
	 *
	 * @date 2020年4月15日 下午1:41:06
	 */
	public int getExponent() {
		return exponent;
	}

	/**
	 * @description 获得动态精度层
	 * @return 动态精度层
	 *
	 * @date 2020年4月15日 下午1:42:03
	 */
	public int getDynamicScaleLayer() {
		return dynamicScaleLayer;
	}

	/**
	 * @description 指定精度层动态自适应偏移量
	 * @return 精度层动态自适应偏移量
	 *
	 * @date 2020年4月15日 下午1:46:20
	 */
	public BigDecimal getDynamicOffsetOfX() {
		return dynamicOffsetOfX;
	}

	/**
	 * @description 获得精度值动态偏移量
	 * @return 精度值动态偏移量
	 *
	 * @date 2020年4月15日 下午1:48:25
	 */
	public BigDecimal getDynamicOffsetOfY() {
		return dynamicOffsetOfY;
	}

	/**
	 * @description 提升动态精度层
	 * @throws Exception 提升动态精度层异常
	 *
	 * @date 2020年4月15日 下午1:49:44
	 */
	public void promoteScaleLayer() throws Exception {
		this.dynamicScaleLayer++;
		try {
			selfAdaptionByDynamicOffsetOfY();
		} catch (Exception e) {
			// 此处回滚没实际意义，但是为了体现业务块的完整性依旧这么处理
			this.dynamicScaleLayer--;
			throw new ExceptionPack("提升动态精度层异常", e);
		}
	}

	/**
	 * @description 获得符合要求的指定精度层的精度偏离量
	 * @param dynamicOffsetOfY
	 * @param dynamicScale
	 * @return 指定精度层的精度偏离量
	 * @throws Exception 获得精度层的精度偏离量异常
	 *
	 * @date 2020年4月13日 下午6:11:33
	 */
	private void selfAdaptionByDynamicOffsetOfY() throws Exception {
		// 根据二项式第二项小于当前的精度层计算精度偏离量
		this.dynamicOffsetOfY = this.dynamicOffsetOfX.multiply(new BigDecimal(this.exponent))
				.multiply(this.base.pow(this.exponent).setScale(this.dynamicScaleLayer + 1));
		try {
			// 比较精度层动态精度偏移量和精度值
			if (dynamicOffsetOfY.compareTo(getDynamicScaleByLayer(dynamicScaleLayer)) < 0) {
				this.dynamicOffsetOfX = this.dynamicOffsetOfX.divide(BigDecimal.TEN);
				selfAdaptionByDynamicOffsetOfY();
			}
		} catch (Exception e) {
			throw new ExceptionPack("获得精度层的精度偏离量异常", e);
		}
	}

	/**
	 * @description 根据精度层获得精度值
	 * @param dynamicScaleLayer 精度层
	 * @return 精度值·······
	 * @throws Exception 精度层异常
	 *
	 * @date 2020年4月15日 上午9:48:00
	 */
	public static final BigDecimal getDynamicScaleByLayer(int dynamicScaleLayer) throws Exception {
		if (dynamicScaleLayer <= 0)
			throw new MathParaException("精度层不能小于一");
		return powWithExpoLen(BigDecimal.TEN, -1 * dynamicScaleLayer, RoundingMode.DOWN).setScale(dynamicScaleLayer);
	}

	/**
	 * @description 次方运算（数学上下文的精度为指数的位数）
	 * @param base         底数
	 * @param exponent     指数
	 * @param roundingMode 近视规则
	 * @return 次方运算的结果
	 * @throws Exception 反射调用获得指数的长度失败
	 *
	 * @date 2020年4月21日 下午4:29:08
	 */
	public static BigDecimal powWithExpoLen(BigDecimal base, int exponent, RoundingMode roundingMode) throws Exception {
		Class<?> zclass = BigDecimal.class;
		try {
			Method method = zclass.getDeclaredMethod("longDigitLength", long.class);
			method.setAccessible(true);
			return base.pow(exponent, new MathContext((int) method.invoke(base, exponent), roundingMode));
		} catch (Exception e) {
			throw new ExceptionPack("通过反射调用BigDecimal的longDigitLength方法异常", e);
		}
	}

	/**
	 * @description toString
	 * @return 输出的对象信息
	 *
	 * @date 2020年4月20日 上午10:09:32
	 */
	@Override
	public String toString() {
		return "SqrtDynamicScalePack [base=" + base + ", exponent=" + exponent + ", dynamicScaleLayer="
				+ dynamicScaleLayer + ", dynamicOffsetOfX=" + dynamicOffsetOfX + ", dynamicOffsetOfY="
				+ dynamicOffsetOfY + "]";
	}

}
